#include <X11/extensions/Xcomposite.h>
#include "e.h"
#include "e_mod_main.h"
#include "compiz.h"
#include "bang.h"
#include "bang_window.h"


//~ # define D(x)  do { \
   //~ now = ecore_time_get(); \
   //~ printf(__FILE__ ":%d[%.4f]  ", __LINE__, now - lastDebug); \
   //~ printf x; \
   //~ lastDebug =  now; \
   //~ fflush(stdout);} \
   //~ while (0)
# define D(x)  ((void) 0)

//# define DE(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define DE(x)  ((void) 0)

//~ #define DN(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define DN(x)  ((void) 0)



double lastDebug;
double now;

static int _bang_window_create_cb(void *data, int ev_type, void *ev);
static int _bang_window_configure_cb(void *data, int ev_type, void *ev);
static int _bang_damage_notify_cb(void *data, int ev_type, void *ev);
static int _bang_window_show_cb(void *data, int ev_type, void *ev);
static int _bang_window_hide_cb(void *data, int ev_type, void *ev);
static int _bang_window_damage_cb(void *data, int ev_type, void *ev);
static int _bang_window_reparent_cb(void *data, int ev_type, void *ev);
static int _bang_window_destroy_cb(void *data, int ev_type, void *ev);
static int _bang_window_focus_in_cb(void *data, int ev_type, void *ev);
static int _bang_window_focus_out_cb(void *data, int ev_type, void *ev);

static int _bang_border_show_cb (void *data, int ev_type, void *ev);
static int _bang_border_hide_cb (void *data, int ev_type, void *ev);

static int _bang_redraw_timer(void *data);
static void handleWindowDamageRect (CompWindow *w, int x, int y, int width, int height);


/**********************************
 *
 * Implementation
 *
 **********************************/
EAPI int
bang_composite_init(Bang *b)
{
   Ecore_X_Display *dpy = ecore_x_display_get();
   CompDisplay     *d = bang->displays->data;
   
   int num = 0;   //TODO DAVE ???? Screen Number?
   
   if (!b) return FALSE;
   
   b->root = e_manager_current_get()->root;

   
   D(("Set display to redirect mode\n"));
   XCompositeRedirectSubwindows (dpy,b->root, CompositeRedirectManual);


   D(("e_manager_new: add screen to display\n"));
   if (!addScreen (d, num, b->root))
   {
      fprintf (stderr, "%s: Failed to manage screen: %d\n", bang->programName, num);
      XCompositeUnredirectSubwindows(dpy, b->root, CompositeRedirectManual);
      return FALSE;
   }
   
   
   
   Ecore_Event_Handler *h;

   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_CONFIGURE, _bang_window_configure_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_CREATE, _bang_window_create_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);

   h = ecore_event_handler_add(ECORE_X_EVENT_DAMAGE_NOTIFY, _bang_damage_notify_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);

   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_DAMAGE, _bang_window_damage_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);

   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_SHOW,_bang_window_show_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_HIDE, _bang_window_hide_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_REPARENT,_bang_window_reparent_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_DESTROY, _bang_window_destroy_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
      
   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_FOCUS_IN, _bang_window_focus_in_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   h = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_FOCUS_OUT, _bang_window_focus_out_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   
   
   h = ecore_event_handler_add(E_EVENT_BORDER_SHOW, _bang_border_show_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   
   h = ecore_event_handler_add(E_EVENT_BORDER_HIDE, _bang_border_hide_cb, b);
   if (h) b->handlers = eina_list_append(b->handlers, h);
   

   b->comp_screen = d->screens->data;   //TODO DAVE fix this
   
   
   bang_redraw(TRUE);
   
   return TRUE;
}

EAPI void
bang_composite_shoutdown(Bang *b)
{
   b->shoutdown = TRUE;
   
#if DEBUG
   ecore_x_error_handler_set(NULL, NULL);
#endif
   
   /* Remove redraw timer */
   if (bang->fps_timer)
      ecore_timer_del(bang->fps_timer);
   
   /* Remove Ecore_X event handlers */
   while (b->handlers)
   {
      DF(("REMOVE HANDLERS\n"));
      ecore_event_handler_del(b->handlers->data);
      b->handlers = eina_list_remove_list(b->handlers, b->handlers);
   }
   
   /* Unload all plugins*/
   unloadPlugins();
   DF(("Unload plugin Done\n"));
   
   /* Unredirect all windows */
   Ecore_X_Display *dpy = ecore_x_display_get();
   XCompositeUnredirectSubwindows(dpy, b->root, CompositeRedirectManual);
   
   /* Free all screens and all windows in all displays */
   while (b->displays)
   {
      CompDisplay *d = b->displays->data;
      removeDisplay(d);
   }
}

void
bang_redraw(int force)
{
   Region         tmpRegion;
   CompScreen     *s;
   CompWindow     *w;
   CompDisplay    *display;
   
   double         time;
   double         timeDiff;
   static double  lastRedraw;
   
   unsigned int   damageMask = 0;
   
   if (!bang) return;
   if (bang->shoutdown) return;
   if (!bang->displays) return;
   
   display = bang->displays->data;
   
   
   /* Check FPS (if we have just done a redraw wait FPS until redraw) */
   time = ecore_time_get();
   timeDiff = time - lastRedraw;
   DN((C_CYA1"-- BEGIN bangRedraw (force: %d timeDiff: %.5f) --\n"C_OFF,force,timeDiff));
   if (timeDiff < MAX_FPS && !force)
   {
      if (!bang->fps_timer)
         bang->fps_timer = ecore_timer_add(MAX_FPS-timeDiff,
                                           _bang_redraw_timer, NULL);
      return;
   }
   lastRedraw = time;
   
   DN((C_CYA1"-- DO bangRedraw (void) --\n"C_OFF));
   
   /* create X empty region */
   tmpRegion = XCreateRegion ();
   if (!tmpRegion)
   {
      fprintf (stderr, "%s: Couldn't create region\n", bang->programName);
      return;
   }

   /* ??? */
   Eina_List *l;
   for (l = display->screens;l ;l = l->next)
   {
      finishScreenDrawing((CompScreen*)l->data);
   }
   
   /* Get damageMask from all screens */
   for (l = display->screens;l ;l = l->next)
   {
      CompScreen *scr = l->data;
      if (!scr->damageMask)
         continue;

      if (!damageMask)
         damageMask |= scr->damageMask;
   }

   if (!damageMask) return;
   
   for (l = display->screens;l ;l = l->next)
   {
      s = l->data;
      
      /* skip screen if has no damage */
      if (!s->damageMask)
         continue;
      DN((C_CYA1"-- REALLY DO bangRedraw (void) --\n"C_OFF));
      s->stencilRef = 0;
      makeScreenCurrent (s);

      (*s->preparePaintScreen) (s, 1000 / defaultRefreshRate);
      
      /* substract top most overlay window region */
      if (s->overlayWindowCount)
      {
         Eina_List *ll;
         for (ll = eina_list_last(s->windows); ll ;ll = ll->prev)
         {
            w = ll->data;
            if (w->destroyed || w->invisible)
               continue;

            if (!w->redirected)
               XSubtractRegion (s->damage, w->region, s->damage);
            break;
         }

         if (s->damageMask & COMP_SCREEN_DAMAGE_ALL_MASK)
         {
            s->damageMask &= ~COMP_SCREEN_DAMAGE_ALL_MASK;
            s->damageMask |= COMP_SCREEN_DAMAGE_REGION_MASK;
         }
      }

      /* region is big as all the screen -> damage the screen */
      if (s->damageMask & COMP_SCREEN_DAMAGE_REGION_MASK)
      {
         XIntersectRegion (s->damage, &s->region, tmpRegion);

         if (tmpRegion->numRects  == 1	 &&
             tmpRegion->rects->x1 == 0	 &&
             tmpRegion->rects->y1 == 0	 &&
             tmpRegion->rects->x2 == s->width &&
             tmpRegion->rects->y2 == s->height)
            damageScreen (s);
      }

      EMPTY_REGION (s->damage);
      
      /* all the screen is damaged -> repaint all */
      if (s->damageMask & COMP_SCREEN_DAMAGE_ALL_MASK)
      {
         s->damageMask = 0;

         (*s->paintScreen) (s,
               &defaultScreenPaintAttrib,
               &s->region,
               PAINT_SCREEN_REGION_MASK |
               PAINT_SCREEN_FULL_MASK);

         glXSwapBuffers (s->display->display, s->root);
      }
      /* repaint only tmpRegion */
      else if (s->damageMask & COMP_SCREEN_DAMAGE_REGION_MASK)
      {
         s->damageMask = 0;

         if ((*s->paintScreen) (s,
                  &defaultScreenPaintAttrib,
                  tmpRegion,
                  PAINT_SCREEN_REGION_MASK))
         {
            BoxPtr pBox;
            int    nBox, y;

            pBox = tmpRegion->rects;
            nBox = tmpRegion->numRects;

            if (s->copySubBuffer)
            {
               while (nBox--)
               {
                  y = s->height - pBox->y2;

                  (*s->copySubBuffer) (s->display->display,
                                       s->root,
                                       pBox->x1, y,
                                       pBox->x2 - pBox->x1,
                                       pBox->y2 - pBox->y1);

                  pBox++;
               }
            }
            else
            {
               glEnable (GL_SCISSOR_TEST);
               glDrawBuffer (GL_FRONT);

               while (nBox--)
               {
                  y = s->height - pBox->y2;

                  glBitmap (0, 0, 0, 0,
                           pBox->x1 - s->rasterX,
                           y - s->rasterY,
                           NULL);

                  s->rasterX = pBox->x1;
                  s->rasterY = y;

                  glScissor (pBox->x1, y,
                           pBox->x2 - pBox->x1,
                           pBox->y2 - pBox->y1);

                  glCopyPixels (pBox->x1, y,
                           pBox->x2 - pBox->x1,
                           pBox->y2 - pBox->y1,
                           GL_COLOR);

                  pBox++;
               }

               glDrawBuffer (GL_BACK);
               glDisable (GL_SCISSOR_TEST);
               glFlush ();
            }
         }
         else
         {
            (*s->paintScreen) (s,
                     &defaultScreenPaintAttrib,
                     &s->region,
                     PAINT_SCREEN_FULL_MASK);

            glXSwapBuffers (s->display->display, s->root);
         }
      }

      (*s->donePaintScreen) (s);

      /* remove destroyed windows */
      while (s->pendingDestroys)
      {
         CompWindow *w;
         Eina_List *l;
         for (l = s->windows;l ;l = l->next)
         {
            w = l->data;
            if (w->destroyed)
            {
               addWindowDamage (w);
               removeWindow (w);
               break;
            }
         }

         s->pendingDestroys--;
      }

   }
   
   DN((C_CYA1"-- END bangRedraw (void) --\n"C_OFF));
}


/**********************************
 *
 * E border callbaks
 *
 **********************************/

static int 
_bang_border_show_cb(void *data, int ev_type, void *ev)
{
   E_Event_Border_Show *e = ev;
   CompWindow *w;
   E_Border *b = e->border;
//   printf("**** SHOW %x ****\n", b->win);
   
   w = findWindowAtDisplay(bang->comp_screen->display, b->win);
   if (!w) return 1;
   w->showed = TRUE;
   damageScreen(w->screen);
   return 1;
}

static int 
_bang_border_hide_cb(void *data, int ev_type, void *ev)
{
   E_Event_Border_Show *e = ev;
   CompWindow *w;
   E_Border *b = e->border;
  // printf("**** HIDE %x ****\n", b->win);
   
   w = findWindowAtDisplay(bang->comp_screen->display, b->win);
   if (!w) return 1;
   w->showed = FALSE;
   damageScreen(w->screen);
   return 1;
}

/**********************************
 *
 * Ecore X window callbacks
 *
 **********************************/

static int
_bang_window_create_cb(void *data, int ev_type, void *ev)
{
   Ecore_X_Event_Window_Create *e = ev;
   Ecore_X_Window_Attributes attr;
   int w, h, x, y;
    
   D((C_RED1"/////////// BEGIN _bang_cb_window_create /////////// id:%x\n"C_OFF,e->win)); 
   
   if (!ecore_x_window_attributes_get(e->win, &attr)) return 0;
   
   D((C_RED1"/////////// DO _bang_cb_window_create /////////// id:%x\n"C_OFF,e->win)); 
   //ecore_x_window_geometry_get(event->win, &x,&y,&w,&h);
   //DE(("/////////// name:%s role:%s pos:%dx%d dim:%dx%d\n",ecore_x_icccm_title_get(event->win),
                                                          //~ ecore_x_icccm_window_role_get(event->win),x,y,w,h));  
   
   if (bang->comp_screen)
   {
      addWindow (bang->comp_screen, e->win, getTopWindow (bang->comp_screen));
   }
   D((C_RED1"/////////// END   _bang_cb_window_create /////////// \n\n"C_OFF)); 
   return 1;
}

static int
_bang_window_configure_cb(void *data, int ev_type, void *ev)
{
   CompWindow *w;
   Ecore_X_Event_Window_Configure *e = ev;

   D((C_YEL1"// BEGIN _bang_cb_window_configure [id:%x][above: %x]\n"C_OFF,e->win,e->abovewin));
   
   //DE(("/////////// name:%s role:%s\n",ecore_x_icccm_title_get(e->win),ecore_x_icccm_window_role_get(e->win)));  
   //DE(("x=%d,y=%d,w=%d,h=%d,border=%d,override=%d,fromwm=%d\n",e->x,e->y,e->w,e->h,e->border,e->override, e->from_wm));

   w = findWindowAtDisplay (bang->comp_screen->display, e->win);
   if (w)
   {
      resizeWindow (w, e->x, e->y, e->w, e->h, e->border);
      
      if (restackWindow (w, e->abovewin))
         addWindowDamage (w);
      
      bang_redraw(FALSE);
   }
   D((C_YEL2"// END   _bang_cb_window_configure //\n"C_OFF));
   return 1;
}

static int
_bang_window_show_cb(void *data, int ev_type, void *ev)
{
   Ecore_X_Event_Window_Show *e = ev;
   CompDisplay *d;
   CompWindow *w;
   
      D((C_BLU1"/////////// BEGIN _bang_cb_window_show /////////// id:%x\n"C_OFF,e->win)); 
   d = bang->comp_screen->display;
   w = findWindowAtDisplay (d, e->win);
   if (!w) return 1;
   
   w->showed = TRUE;
   if (!w->e_attrib.input_only)
   {
      D((C_BLU1"/////////// BEGIN _bang_cb_window_show /////////// id:%x\n"C_OFF,e->win)); 
      D((C_BLU1"/////////// name:%s role:%s\n"C_OFF,ecore_x_icccm_title_get(e->win),ecore_x_icccm_window_role_get(e->win)));  
      mapWindow (w);
      //bangRedraw(FALSE);
      D((C_BLU1"/////////// END   _bang_cb_window_show /////////// \n\n"C_OFF)); 
   }
   
   
   return 1;
}

static int
_bang_window_hide_cb(void *data, int ev_type, void *ev)
{
   CompDisplay *d;
   CompWindow *w;
   Ecore_X_Event_Window_Hide *e = ev;
D((C_BLU2"/////////// BEGIN _bang_cb_window_hide /////////// id:%x\n"C_OFF,e->win)); 
   d = bang->comp_screen->display;
   w = findWindowAtDisplay (d, e->win);
   if (!w) return 1;
   
   w->showed = FALSE;
   if (!w->e_attrib.input_only)
   {
      D((C_BLU2"/////////// BEGIN _bang_cb_window_hide /////////// id:%x\n"C_OFF,e->win)); 
      unmapWindow (w);
      bang_redraw(TRUE);
      D((C_BLU2"/////////// END   _bang_cb_window_hide /////////// \n\n"C_OFF)); 
   }
   
   return 1;
}

static int
_bang_window_reparent_cb(void *data, int ev_type, void *ev)
{
   Ecore_X_Event_Window_Reparent *e = ev;
   D(("/////////// BEGIN _bang_cb_window_reparent /////////// [id:%x][parent:%x]\n",e->win,e->parent)); 

   CompScreen *s = bang->comp_screen;
   CompDisplay *d = s->display; 
   CompWindow *w;
    
   w = findWindowAtDisplay (d, e->win);
   if(w)
   {
      if (e->parent != bang->root)
      {
         destroyWindow (w);
        // bangRedraw(FALSE);
      }
   }

   D(("/////////// END   _bang_cb_window_reparent /////////// \n\n")); 
   return 1;
}

static int
_bang_window_destroy_cb(void *data, int ev_type, void *ev)
{
   Ecore_X_Event_Window_Destroy *e = ev;
   CompDisplay *d;
   CompWindow *w;

   d = bang->comp_screen->display;
   D((C_RED2"/////////// BEGIN _bang_cb_window_destroy /////////// id:%x\n"C_OFF,e->win)); 
   
   w = findWindowAtDisplay (d, e->win);
   if (w)
   {
      destroyWindow (w);
   }
   
   D((C_RED2"/////////// END   _bang_cb_window_destroy /////////// \n"C_OFF));
   return 1;
}

static int
_bang_damage_notify_cb(void *data, int ev_type, void *ev)
{
   DE(("/////////// BEGIN _bang_cb_damage_notify /////////// \n")); 
   Ecore_X_Event_Damage *e = ev;
   CompDisplay *d;
   CompWindow *w;
   
   d = bang->comp_screen->display;

//DE(("_bang_cb_damage_notify: Level %d\n",e->level)); 
//DE(("_bang_cb_damage_notify: area: x=%d,y=%d,w=%d,h=%d \n",e->area.x,e->area.y,e->area.width,e->area.height)); 
//DE(("_bang_cb_damage_notify: geom: x=%d,y=%d,w=%d,h=%d \n",e->geometry.x,e->geometry.y,e->geometry.width,event->geometry.height));

//DE(("   e->type == d->damageEvent + XDamageNotify\n"));

   if (lastDamagedWindow && e->drawable == lastDamagedWindow->id)
      w = lastDamagedWindow;
   else
   {
      w = findWindowAtDisplay (d, e->drawable);
      if (w)
         lastDamagedWindow = w;
   }

   if (!w) return 0;
   
   w->texture.oldMipmaps = TRUE;
   
   handleWindowDamageRect(w,
                          e->area.x,
                          e->area.y,
                          e->area.width,
                          e->area.height);

   bang_redraw(FALSE);
   //D(("/////////// END   _bang_cb_damage_notify /////////// \n\n")); 
return 1;
}

static int
_bang_window_damage_cb(void *data, int ev_type, void *ev)
{
   DN(("/////////// BEGIN _bang_cb_window_damage /////////// \n")); 
   Ecore_X_Event_Window_Damage *e = ev;
   CompDisplay *d;
   CompScreen *s;

   d = bang->comp_screen->display;
   s = bang->comp_screen;
    
   if (s)
   {
      int more = e->count + 1;

      if (s->nExpose == s->sizeExpose)
      {
         if (s->exposeRects)
         {
            // DN(("handleEvent() s->exposeRects\n"));
            s->exposeRects = realloc(s->exposeRects,
                             (s->sizeExpose + more) * sizeof (XRectangle));
            s->sizeExpose += more;
         }
         else
         {
            // DN(("handleEvent() !s->exposeRects\n"));
            s->exposeRects = E_NEW(XRectangle, more);
            s->sizeExpose = more;
         }
      }

      s->exposeRects[s->nExpose].x      = e->x;
      s->exposeRects[s->nExpose].y      = e->y;
      s->exposeRects[s->nExpose].width  = e->w;
      s->exposeRects[s->nExpose].height = e->h;
      s->nExpose++;

      if (e->count == 0)
      {
         //DN(("handleEvent() event->xexpose.count == 0\n"));
         REGION rect;

         rect.rects = &rect.extents;
         rect.numRects = rect.size = 1;

         while (s->nExpose--)
         {
            rect.extents.x1 = s->exposeRects[s->nExpose].x;
            rect.extents.y1 = s->exposeRects[s->nExpose].y;
            rect.extents.x2 = rect.extents.x1 +
                              s->exposeRects[s->nExpose].width;
            rect.extents.y2 = rect.extents.y1 +
                              s->exposeRects[s->nExpose].height;

            damageScreenRegion (s, &rect);
         }
         s->nExpose = 0;
      }
     // bang_redraw(TRUE);
   }
   DN(("/////////// END  _bang_cb_window_damage /////////// \n\n")); 
   return 1;
}


static int
_bang_window_focus_in_cb(void *data, int type, void *ev)
{
   Ecore_X_Event_Window_Focus_In *e = ev;
   CompDisplay *d;
   CompWindow *w;

   D(("/////////// BEGIN _bang_cb_window_focus_in  [id:%x]///////////\n",e->win));

   d = bang->comp_screen->display;
   
//   w = findWindowAtDisplay (d, e->win);
//   if (!w) return 1;
   
   return 1;
}

static int
_bang_window_focus_out_cb(void *data, int type, void *ev)
{
   Ecore_X_Event_Window_Focus_Out *e = ev;
   CompDisplay *d;
   CompWindow *w;
   
   D(("/////////// BEGIN _bang_cb_window_focus_out  [id:%x]///////////\n",e->win));

   d = bang->comp_screen->display;
      
   //w = findWindowAtDisplay (d, e->win);
   //if (!w) return 1;
   
   return 1;
}

/**********************************
 *
 * Private functions
 *
 **********************************/

static int
_bang_redraw_timer(void *data)
{
   ecore_timer_del(bang->fps_timer);
   bang->fps_timer = NULL;
   bang_redraw(TRUE);
   return 0;//0 = don't call again
}

static void
handleWindowDamageRect (CompWindow *w,
         int      x,
         int      y,
         int      width,
         int      height)
{
   REGION region;
   Bool   initial = FALSE;

   if (!w->redirected)
      return;

   if (!w->damaged)
   {
      w->damaged = initial = TRUE;
      w->invisible = WINDOW_INVISIBLE (w);
   }

   region.extents.x1 = x;
   region.extents.y1 = y;
   region.extents.x2 = region.extents.x1 + width;
   region.extents.y2 = region.extents.y1 + height;

   if (!(*w->screen->damageWindowRect) (w, initial, &region.extents))
   {
      region.extents.x1 += w->attrib.x + w->attrib.border_width;
      region.extents.y1 += w->attrib.y + w->attrib.border_width;
      region.extents.x2 += w->attrib.x + w->attrib.border_width;
      region.extents.y2 += w->attrib.y + w->attrib.border_width;

      region.rects = &region.extents;
      region.numRects = region.size = 1;

      damageWindowRegion (w, &region);
   }
}

